<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Writing a Package in Python</title>
<link rel="stylesheet" href="../format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="Python, programming, package">
<meta name="description" content="Writing a Package in Python">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

</head>


<body>

<div class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* NewVertical */
google_ad_slot = "4231459001";
google_ad_width = 120;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">

<a href="/" title="Home">Home</a>


<h1>Writing a Package in Python</h1>

<p>
by <i>Tarek Ziadé</i>
</p>

<p>
<a href="http://www.packtpub.com/expert-python-programming/book/mid/070409pxiqhr">
<img style="float: left; margin-bottom:30px; margin-right:30px" src="../images/expertpython.jpeg" alt="Expert Python programming"></a> 
</p>

<p>
Intents of this article are:
</p>

<ul>
<li>To shorten the time needed to set up everything before starting the 
real work, in other words the boiler-plate code</li>
<li>To provide a standardized way to write packages</li>
<li>To ease the use of a test-driven development approach</li>
<li>To facilitate the releasing process</li>
</ul>



<br>
<p>
It is organized in the following four parts:
</p>

<ul> 
<li>
A <b>common pattern</b> for all packages that describes the similarities between 
all Python packages, and how <i>distutils</i> and <i>setuptools</i> 
play a central role
</li>
<li>
How <b>generative programming</b> (<a href="http://en.wikipedia.org/wiki/Generative_programming">
http://en.wikipedia.org/wiki/Generative_programming</a>) 
can help this through the template-based approach
</li>
<li>The package template creation, where everything needed to work is set</li>
<li>Setting up a development cycle</li>
</ul>



<h3>A Common Pattern for All Packages</h3>

<p>
The easiest way to organize the code of an application is to split it into several packages using eggs. 
This makes the code simpler, and easier to understand, maintain, and change. 
It also maximizes the reusability of each package. They act like components.
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<p>
Applications for a given company can have a set of eggs glued together with a master egg.
</p>

<p>
Therefore, all packages can be built using egg structures.
</p>

<p>
This section presents how a <b>namespaced package</b> is organized, released,
and distributed to the world through distutils and setuptools.
</p>

<p>
Writing an egg is done by layering the code in a nested folder that provides 
a common prefix namespace. For instance, for the Acme company, 
the common namespace can be acme. The result is a namespaced package.
</p>

<p>
For example, a package whose code relates to SQL can be called acme.sql. 
The best way to work with such a package is to create an acme.sql folder 
that contains the acme and then the sql folder:
</p>



<h4>setup.py, the Script That Controls Everything</h4>

<p>
The root folder contains a <i>setup.py</i> script, which defines all metadata 
as described in the distutils module, combined as arguments in a call
to the standard setup function. This function was extended by 
the third-party library setuptools that provides most of the egg infrastructure.
</p>

<p>
<i>The boundary between distutils and setuptools is getting fuzzy, and they might merge one day.</i>
</p>

<p>
Therefore, the minimum content for this file is:
</p>

<pre>
from setuptools import setup
setup(name='acme.sql')
</pre>

<p>
<i>name</i> gives the full name of the egg. From there, the script provides several 
commands that can be listed with the <i>-help-commands</i> option.
</p>

<pre class="code">
$ python setup.py --help-commands

Standard commands:
  build             build everything needed to install
  ...
  install           install everything from build directory
  sdist         create a source distribution
  register      register the distribution
  bdist         create a built (binary) distribution
Extra commands:
  develop       install package in 'development mode'
  ...
  test          run unit tests after in-place build
 alias         define a shortcut
  bdist_egg     create an "egg" distribution

</pre>

<p>
The most important commands are the ones left in the preceding listing.
<b>Standard commands</b> are the built-in commands provided by <i>distutils</i>, 
whereas <b>Extra commands</b>. are the ones created by third-party packages
such as <i>setuptools</i> or any other package that defines and registers a new command.
</p>


<h4>sdist</h4>

<p>
The <i>sdist</i> command is the simplest command available. It creates a release tree
where everything needed to run the package is copied. This tree is then
archived in one or many archived files (often, it just creates one tar ball). 
The archive is basically a copy of the source tree.
</p>

<p>
This command is the easiest way to distribute a package from the target system independently.
It creates a <i>dist</i> folder with the archives in it that can be distributed. 
To be able to use it, an extra argument has to be passed to <i>setup</i> to provide 
a <i>version</i> number. If you don't give it a version value, it will use <i>version = 0.0.0:</i>
</p>

<pre>
from setuptools import setup
setup(name='acme.sql', version='0.1.1'
</pre>

<p>
This number is useful to upgrade an installation.
Every time a package is released,
the number is raised so that the target system knows it has changed.
</p>

<p>
Let's run the <i>sdist</i> command with this extra argument:
</p>

<pre class="code">
$ python setup.py sdist
running sdist
...
creating dist
tar -cf dist/acme.sql-0.1.1.tar acme.sql-0.1.1
gzip -f9 dist/acme.sql-0.1.1.tar
removing 'acme.sql-0.1.1' (and everything under it)
$ ls dist/
acme.sql-0.1.1.tar.gz
</pre>

<i>Under Windows, the archive will be a ZIP file.</i>

<p>
The version is used to mark the name of the archive, which can be distributed 
and installed on any system having Python. In the sdist distribution, 
if the package contains C libraries or extensions, the target system 
is responsible for compiling them. This is very common for Linux-based systems
or Mac OS because they commonly provide a compiler. But it is less usual
to have it under Windows. That's why a package should always be distributed 
with a pre-built distribution as well, when it is 
intended to run under several platforms.
</p>


<h3>The MANIFEST.in File</h3>

<p>
When building a distribution with <i>sdist</i>, <i>distutils</i> browse the package directory 
looking for files to include in the archive.
</p>

<i>distutils</i> will include:

<ul>
<li>All Python source files implied by the <i>py_modules</i>, <i>packages</i>, and <i>scripts</i> option</li>
<li>All C source files listed in the <i>ext_modules</i> option</li>
<li>Files that match the glob pattern <i>test/test*.py</i></li>
<li>README, <i>README.txt</i>, <i>setup.py</i>, and <i>setup.cfg</i> files</li>
</ul>

<p>
Besides, if your package is under Subversion or CVS, <i>sdist</i> will browse folders
such as <i>.svn</i> to look for files to include <i>.sdist</i> builds a <i>MANIFEST</i> file 
that lists all files and includes them into the archive.
</p>

<p>
Let's say you are not using these version control systems, and need to
include more files. Now, you can define a template called <i>MANIFEST.in</i> 
in the same directory as that of <i>setup.py</i> for the <i>MANIFEST</i> file,
where you indicate to <i>sdist</i> which files to include.
</p>

<p>
This template defines one inclusion or exclusion rule per line, for example:
</p>

<pre>
include HISTORY.txt
include README.txt
include CHANGES.txt
include CONTRIBUTORS.txt
include LICENSE
recursive-include *.txt *.py
</pre>

<p>
The full list of commands is available at 
<a href="http://docs.python.org/dist/sdist-cmd.html#sdist-cmd.">http://docs.python.org/dist/sdist-cmd.html#sdist-cmd.</a>
</p>


<h4>build and bdist</h4>

<p>
To be able to distribute a pre-built distribution, <i>distutils</i> provide the <i>build</i> command, 
which compiles the package in four steps:
</p>

<ul>
<li>
<i>build_py</i>: Builds pure Python modules by byte-compiling 
them and copying them into the build folder.
</li>
<li>
<i>build_clib</i>: Builds C libraries, when the package contains any, 
using Python compiler and creating a static library in the build folder.
</li>
<li>
<i>build_ext</i>: Builds C extensions and puts the result in the 
build folder like <i>build_clib</i>.
</li>
<li>
<i>build_scripts</i>: Builds the modules that are marked as scripts. 
It also changes the interpreter path when the first line was set (!#) 
and fixes the file mode so that it is executable.
</li>
</ul>

<p>
Each of these steps is a command that can be called independently. 
The result of the compilation process is a build folder that contains 
everything needed for the package to be installed. There's no cross-compiler 
option yet in the <i>distutils</i> package. This means that the result of the 
command is always specific to the system it was build on.
</p>

<p>
<i>
Some people have recently proposed patches in the Python tracker to make
distutils able to cross-compile the C parts. So this feature might be 
available in the future.
</i>
</p>

<p>
When some C extensions have to be created, the build process uses the 
system compiler and the Python header file (<i>Python.h</i>). This include 
file is available from the time Python was built from the sources. 
For a packaged distribution, an extra package called 
<i>python-dev</i> often contains it, and has to be installed as well.
</p>

<p>
The C compiler used is the system compiler. For Linux-based system 
or Mac OS X, this would be <b>gcc</b>. For Windows, Microsoft Visual C++ can be used 
(there's a free command-line version available) and the open-source project MinGW as well. 
This can be configured in distutils.
</p>

<p>
The <i>build</i> command is used by the <i>bdist</i> command to build a binary distribution. 
It calls <i>build</i> and all dependent commands, and then creates an archive in the same was as <i>sdist</i> does.
</p>

<p>
Let's create a binary distribution for <i>acme.sql</i> under Mac OS X:
</p>

<pre class="code">
$ python setup.py bdist
running bdist
running bdist_dumb
running build
...
running install_scripts
tar -cf dist/acme.sql-0.1.1.macosx-10.3-fat.tar .
gzip -f9 acme.sql-0.1.1.macosx-10.3-fat.tar
removing 'build/bdist.macosx-10.3-fat/dumb' (and everything under it)
$ ls dist/
acme.sql-0.1.1.macosx-10.3-fat.tar.gz    acme.sql-0.1.1.tar.gz
</pre>

<p>
Notice that the newly created archive's name contains the name of the 
system and the distribution it was built under (Mac OS X 10.3).
</p>
<p>
The same command called under Windows will create a specific distribution archive:
</p>

<pre class="code">
C:acme.sql> python.exe setup.py bdist
...
C:acme.sql> dir dist
25/02/2008  08:18     &lt;DIR&gt;          .
25/02/2008  08:18     &lt;DIR&gt;          ..
25/02/2008  08:24             16 055 acme.sql-0.1.win32.zip
               1 File(s)          16 055 bytes
               2 Dir(s)   22 239 752 192 bytes free
</pre>

<p>
If a package contains C code, apart from a source distribution, 
it's important to release as many different binary distributions as possible.
At the very least, a Windows binary distribution is important 
for those who don't have a C compiler installed.
</p>

<p>
A binary release contains a tree that can be copied directly into the Python tree.
It mainly contains a folder that is copied into Python's site-packages folder.
</p>


<h4>bdist_egg</h4>

<p>
The <i>bdist_egg</i> command is an extra command provided by setuptools. 
It basically creates a binary distribution like <i>bdist</i>, but with a 
tree comparable to the one found in the source distribution.
In other words, the archive can be downloaded, uncompressed,
and used as it is by adding the folder to the Python search path (<i>sys.path</i>).
</p>

<p>
These days, this distribution mode should be used instead of the bdist-generated one.
</p>

<h4>install</h4>

<p>
The <i>install</i> command installs the package into Python. 
It will try to build the package if no previous build was made and
then inject the result into the Python tree. When a source distribution
is provided, it can be uncompressed in a temporary folder and then
installed with this command. The <i>install</i> command will also install 
dependencies that are defined in the <i>install_requires></i> metadata.
</p>

<p>
This is done by looking at the packages in the Python Package Index (PyPI). 
For instance, to install <i>pysqlite</i> and <i>SQLAlchemy</i> together with <i>acme.sql</i>,
the setup call can be changed to:
</p>

<pre>
from setuptools import setup
setup(name='acme.sql', version='0.1.1',
      install_requires=['pysqlite', 'SQLAlchemy'])
</pre>

<p>
When we run the command, both dependencies will be installed.
</p>

<h4>How to Uninstall a Package</h4>

<p>
The command to uninstall a previously installed package is missing in <i>setup.py</i>. 
This feature was proposed earlier too. This is not trivial 
at all because an installer might change files that are used
by other elements of the system.
</p>

<p>
The best way would be to create a snapshot of all elements that are being changed, 
and a record of all files and directories created.
</p>

<p>
A <i>record</i> option exists in <i>install</i> to record all files that have been created in a text file:
</p>

<pre>
$ python setup.py install --record installation.txt
running install
...
writing list of installed files to 'installation.txt'
</pre>

<p>
This will not create any backup on any existing file, so removing the file
mentioned might break the system. There are platform-specific solutions 
to deal with this. For example, <i>distutils</i> allow you to distribute the 
package as an RPM package. But there's no universal way to handle it as yet.
</p>

<p>
The simplest way to remove a package at this time is to erase the files created, 
and then remove any reference in the <i>easy-install.pth</i> file
that is located in the <i>sitepackages</i> folder.
</p>

<h4>develop</h4>

<p>
<i>setuptools</i> added a useful command to work with the package.
The <i>develop</i> command builds and installs the package in place, 
and then adds a simple link into the Python <i>site-packages</i> folder.
This allows the user to work with a local copy of the code, 
even though it's available within Python's <i>site-packages</i> folder. 
All packages that are being created are linked with the
develop command to the interpreter.
</p>

<p>
When a package is installed this way, it can be removed specifically 
with the <i>-u</i> option, unlike the regular install:
</p>

<pre>
$ sudo python setup.py develop
running develop
...
Adding iw.recipe.fss 0.1.3dev-r7606 to easy-install.pth file
Installed /Users/repos/ingeniweb.sourceforge.net/iw.recipe.fss/trunk
Processing dependencies ...
$ sudo python setup.py develop -u
running develop
Removing
...
Removing iw.recipe.fss 0.1.3dev-r7606 from easy-install.pth file
</pre>

<p>
Notice that a package installed with <i>develop</i> will always prevail 
over other versions of the same package installed.
</p>

<h4>test</h4>

<p>
Another useful command is test. It provides a way to run all tests contained
in the package. It scans the folder and aggregates the test suites it finds.
The test runner tries to collect tests in the package but is quite limited. 
A good practice is to hook an extended test runner such as zope.testing
or Nose that provides more options.
</p>

<p>
To hook Nose transparently to the test command, the test_suite metadata
can be set to 'nose.collector' and Nose added in the test_requires list:
</p>

<pre>
setup(
...
test_suite='nose.collector',
test_requires=['Nose'],
...
)
</pre>


<h4>register and upload</h4>

<p>
To distribute a package to the world, two commands are available:
</p>

<ul>
<li><i>register</i>: This will upload all metadata to a server.</li>
<li><i>upload</i>: This will upload to the server all archives previously built in the dist folder.</li>
</ul>


<p>
The main PyPI server, previously named the Cheeseshop, is located at 
<a href="http://pypi.python.org/pypi">http://pypi.python.org/pypi</a> 
and contains over 3000 packages from the community. It is a default 
server used by the <i>distutils</i> package, and an initial call to the
register command will generate a <i>.pypirc</i> file in your home directory.
</p>

<p>
Since the PyPI server authenticates people, when changes are made to a package,
you will be asked to create a user over there. This can also be done at the prompt:
</p>

<pre>
$ python setup.py register
running register
...
We need to know who you are, so please choose either:
 1. use your existing login,
 2. register as a new user,
 3. have the server generate a new password for you (and email it to
you), or
 4. quit
Your selection [default 1]:
</pre>

<p>
Now, a <i>.pypirc</i> file will appear in your home directory containing the
user and assword you have entered. These will be used every time 
<i>register</i> or <i>upload</i> is called:
</p>

<pre>
[server-index]
username: tarek
password: secret
</pre>

<i>
There is a bug on Windows with Python 2.4 and 2.5. The home directory 
is not found by distutils unless a HOME environment variable is added.
But, this has been fixed in 2.6. To add it, use the technique where 
we modify the PATH variable. Then add a HOME variable for your user 
that points to the directory returned by os.path.expanduser('~').
</i>

<p>
When the <i>download_url</i> metadata or the url is specified, and is a
valid URL, the PyPI server will make it available to the users 
on the project web page as well.
</p>

<p>
Using the <i>upload</i> command will make the archive directly available 
at PyPI, so the <i>download_url</i> can be omitted:
</p>

<p>
Distutils defines a Trove categorization 
(see PEP 301: <a href="http://www.python.org/dev/peps/pep-0301/#distutils-trove-classification">
http://www.python.org/dev/peps/pep-0301/#distutils-trove-classification</a>) 
to classify the packages, such as the one defined at Sourceforge. 
The trove is a static list that can be found at <a href="http://pypi.python.org/pypi?%3Aaction=list_classifiers">
http://pypi.python.org/pypi?%3Aaction=list_classifiers</a>, 
and that is augmented from time to time with a new entry.
</p>

<p>
Each line is composed of levels separated by "::":
</p>

<pre>
...
Topic :: Terminals
Topic :: Terminals :: Serial
Topic :: Terminals :: Telnet
Topic :: Terminals :: Terminal Emulators/X Terminals
Topic :: Text Editors Topic :: Text Editors :: Documentation
Topic :: Text Editors :: Emacs
...
</pre>

<p>
A package can be classified in several categories, which can be listed in the classifiers meta-data. 
A GPL package that deals with low-level Python code (for instance) can use:
</p>

<pre>
Programming Language :: Python
Topic :: Software Development :: Libraries :: Python Modules
License :: OSI Approved :: GNU General Public License (GPL)
</pre>

<h4>Python 2.6 .pypirc Format</h4>

<p>
The <i>.pypirc</i> file has evolved under Python 2.6, so several users and their passwords 
can be managed along with several PyPI-like servers. A Python 2.6 configuration 
file will look somewhat like this:
</p>

<pre>
[distutils]
index-servers =
    pypi
    alternative-server
    alternative-account-on-pypi
[pypi]
username:tarek
password:secret
[alternative-server]
username:tarek
password:secret
repository:http://example.com/pypi
</pre>

<p>
The <i>register</i> and <i>upload</i> commands can pick a server with the help of the <i>-r</i> option, 
using the repository full URL or the section name:
</p>

<pre>
# upload to http://example.com/pypi
$ python setup.py sdist upload -r   alternative-server
#  registers with default account (tarek at pypi)
$ python setup.py register
#  registers to http://example.com
$ python setup.py register -r http://example.com/pypi
</pre>

<p>
This feature allows interaction with servers other than PyPI. 
When dealing with a lot of packages that are not to be published 
at PyPI, a good practice is to run your own PyPI-like server. 
The Plone Software Center (see <a href="http://plone.org/products/plonesoftwarecenter">
http://plone.org/products/plonesoftwarecenter</a>)
can be used, for example, to deploy a web server that can interact
with distutils <i>upload</i> and <i>register</i> commands.
</p>

<h4>Creating a New Command</h4>

<p>
<i>distutils</i> allows you to create new commands, as described in 
<a href="http://docs.python.org/dist/node84.html">http://docs.python.org/dist/node84.html</a>.
A new command can be registered with an entry point, which was introduced by
<i>setuptools</i> as a simple way to define packages as plug-ins.
</p>

<p>
An entry point is a named link to a class or a function that
is made available through some APIs in setuptools.
Any application can scan for all registered packages and use 
the linked code as a plug-in.
</p>

<p>
To link the new command, the <i>entry_points</i> metadata can be used in the setup call:
</p>

<pre>
setup(name="my.command",
          entry_points="""
             [distutils.commands]
             my_command = my.command.module.Class
          """)
</pre>

<p>
All named links are gathered in named sections. When <i>distutils</i>
is loaded, it scans for links that were registered under
<i>distutils.commands</i>.
</p>

<p>
This mechanism is used by numerous Python applications
that provide extensibility.
</p>

<h4>setup.py Usage Summary</h4>

<p>
There are three main actions to take with <i>setup.py</i>:
</p>

<ul>
<li>Build a package.</li>
<li>Install it, possibly in develop mode.</li>
<li>Register and upload it to PyPI.</li>
</ul>


<p>
Since all the commands can be combined in the same call, 
some typical usage patterns are:
</p>

<pre>
# register the package with PyPI, creates a source and
# an egg distribution, then upload them
$ python setup.py register sdist bdist_egg upload
# installs it in-place, for development purpose
$ python setup.py develop
# installs it
$ python setup.py install
</pre>

<h4>The alias Command</h4>

<p>
To make the command line work easily, a new command has
been introduced by <i>setuptools</i> called <i>alias</i>. In a file 
called <i>setup.cfg</i>, it creates an alias for a given combination 
of commands. For instance, a release command can be created
to perform all actions needed to upload a source and a 
binary distribution to PyPI:
</p>

<pre>
$ python setup.py alias release register sdist bdist_egg upload
running alias
Writing setup.cfg
$ python setup.py release
...
</pre>

<h4>Other Important Metadata</h4>

<p>
Besides the name and the version of the package being distributed, 
the most important arguments setup can receive are:
</p>

<ul>
<li><i>description</i>: A few sentences to describe the package</li>
<li><i>long_description</i>: A full description that can be in reStructuredText</li>
<li><i>keywords</i>: A list of keywords that define the package</li>
<li><i>author</i>: The author's name or organization</li>
<li><i>author_email</i>: The contact email address</li>
<li><i>url</i>: The URL of the project</li>
<li><i>license</i>: The license (GPL, LGPL, and so on)</li>
<li><i>packages</i>: A list of all names in the package; <i>setuptools</i> 
provides a small function called <i>find_packages</i> that calculates this</li>
<li><i>namespace_packages</i>: A list of namespaced packages</li>
</ul>

<p>
A completed <i>setup.py</i> file for <i>acme.sql</i> would be:
</p>

<pre class="code">
import os
from setuptools import setup, find_packages
version = '0.1.0'
README = os.path.join(os.path.dirname(__file__), 'README.txt')
long_description = open(README).read() + 'nn'
setup(name='acme.sql',
      version=version,
      description=("A package that deals with SQL, "
                    "from ACME inc"),
      long_description=long_description,
      classifiers=[
        "Programming Language :: Python",
        ("Topic :: Software Development :: Libraries ::
          "Python Modules"),
        ],
      keywords='acme sql',
      author='Tarek',
      author_email='tarek@ziade.org',
      url='http://ziade.org',
      license='GPL',
      packages=find_packages(),
      namespace_packages=['acme'],
      install_requires=['pysqlite','SQLAchemy']
      )
</pre>

<p>
<i>
The two comprehensive guides to keep under your pillow are: 
The distutils guide at <a href="http://docs.python.org/dist/dist.html">
http://docs.python.org/dist/dist.html</a>
The setuptools guide at <a href="http://peak.telecommunity.com/DevCenter/setuptools">
http://peak.telecommunity.com/DevCenter/setuptools</a>.
</i>
</p>

<br>
<hr style="width:60%">
<br>

<p>
This article is extracted from:
<b>Expert Python Programming</b>
<br>
Best practices for designing, coding, and distributing your Python software
</p>

<ul>
<li>Learn Python development best practices from an expert, with detailed coverage of naming and coding conventions</li>
<li>Apply object-oriented principles, design patterns, and advanced syntax tricks</li>
<li>Manage your code with distributed version control</li>
<li>Profile and optimize your code</li>
<li>Practice test-driven development and continuous integration</li>
</ul>



<p>
For more information, please visit:
<a href="http://www.packtpub.com/expert-python-programming/book/mid/070409pxiqhr">
http://www.PacktPub.com/expert-python-programming/book</a>
</p>


<div class="center">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>
<br>


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> created April 1, 2009  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</body>
</html>

